/*******************************************************************************
 * Copyright (c) 2008, 2016 Symbian Software Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Andrew Ferguson (Symbian) - Initial implementation
 * Martin Stumpf - adapted orginal to cope with single line comments
 *******************************************************************************/
package org.eclipse.cdt.ui.text.doctools.doxygen;


import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;

import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTNodeSelector;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.ui.text.ICPartitions;

/**
 * {@link IAutoEditStrategy} for adding Doxygen tags for comments.
 *
 * @since 5.11
 * @noextend This class is not intended to be subclassed by clients.
 */
public class DoxygenSingleAutoEditStrategy extends DoxygenMultilineAutoEditStrategy {
    private static final String SLASH_COMMENT = "///";  //$NON-NLS-1$
    private static final String EXCL_COMMENT = "//!"; //$NON-NLS-1$
    private static String fgDefaultLineDelim = "\n"; //$NON-NLS-1$


    public DoxygenSingleAutoEditStrategy() {
    }

    /**
     * @see org.eclipse.jface.text.IAutoEditStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
     */
    @Override
	public void customizeDocumentCommand(IDocument doc, DocumentCommand cmd) {
        fgDefaultLineDelim = TextUtilities.getDefaultLineDelimiter(doc);
        if(doc instanceof IDocumentExtension4) {
            boolean forNewLine= cmd.length == 0 && cmd.text != null && endsWithDelimiter(doc, cmd.text);

            if(forNewLine ) {
                IDocumentExtension4 ext4= (IDocumentExtension4) doc;
                DocumentRewriteSession drs= ext4.startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED_SMALL);
                try {
                    customizeDocumentAfterNewLine(doc, cmd);
                } finally {
                    ext4.stopRewriteSession(drs);
                }
            }
        }
    }

    @Override
	public void customizeDocumentAfterNewLine(IDocument doc, final DocumentCommand c) {
        int offset= c.offset;
        if (offset == -1 || doc.getLength() == 0)
            return;

        final StringBuilder buf= new StringBuilder(c.text);
        try {
            IRegion line= doc.getLineInformationOfOffset(c.offset);
            String lineDelimiter = doc.getLineDelimiter(doc.getLineOfOffset(c.offset));
            int lineDelimiterLength = lineDelimiter.length();

            
            IRegion prefix= findPrefixRange(doc, line);
            String indentationWithPrefix = doc.get(prefix.getOffset(), prefix.getLength());
            String commentPrefix = getCommentPrefix(indentationWithPrefix);
            String commentContent = doc.get(prefix.getOffset() + prefix.getLength(), 
            		line.getLength() - prefix.getLength());
            //String commentContentBeforeCursor = doc.get(prefix.getOffset() + prefix.getLength(), 
            //		c.offset - line.getOffset() - prefix.getLength());
            String commentContentBehindCursor = doc.get(c.offset, 
            		line.getLength() - (c.offset - line.getOffset()));
 
            buf.append(indentationWithPrefix);


            boolean commentAtStart = prefix.getOffset() + prefix.getLength() <= c.offset;
            boolean commentFollows = false;
            boolean commentAhead = false;
            boolean firstLineContainsText = commentContent.trim().length() > 0;

            if (commentAtStart) {
                if (line.getOffset() + line.getLength() + lineDelimiterLength < doc.getLength())
                {
                    IRegion nextLine = doc.getLineInformationOfOffset(line.getOffset() + line.getLength() + lineDelimiterLength);
                    commentFollows = doc.get(nextLine.getOffset(), nextLine.getLength()).trim().startsWith(commentPrefix);

                    if (line.getOffset() >= 1)
                    {
                        IRegion previousLine = doc.getLineInformationOfOffset(line.getOffset() - 1);
                        commentAhead = doc.get(previousLine.getOffset(), previousLine.getLength()).trim().startsWith(commentPrefix);
                    }
                }
                // comment started on this line
                buf.append(" "); //$NON-NLS-1$
            }

            c.shiftsCaret= false;
            c.caretOffset= c.offset + buf.length();

            if(commentAtStart && !commentFollows && !commentAhead) {
                try {
                	StringBuilder content = getDeclarationLines(doc, offset);

                	boolean contentAlreadyThere = (firstLineContainsText && content != null && content.toString().contains(commentContentBehindCursor.trim()));
                    if (content == null || content.toString().trim().length() == 0 || contentAlreadyThere)
                    {
                        buf.setLength(0);
                        buf.append(fgDefaultLineDelim);
                        buf.append(indentationWithPrefix).append(' ');
                        c.shiftsCaret= false;
                        c.caretOffset= c.offset + buf.length();
                    } else {
                        if (!firstLineContainsText)
                        {
                            c.shiftsCaret= false;
                            c.caretOffset= c.offset + 1;
                            buf.setLength(0);
                            buf.append(' ').append(
                            		indent(content, indentationWithPrefix + " ", //$NON-NLS-1$
                            		fgDefaultLineDelim).substring((indentationWithPrefix + " ").length())); //$NON-NLS-1$
                        }
                        else
                        {
                            buf.append(fgDefaultLineDelim);
                            buf.append(indent(content, indentationWithPrefix + " ", fgDefaultLineDelim)); //$NON-NLS-1$
                        }

                        buf.setLength(buf.length() - fgDefaultLineDelim.length());
                    }
                } catch(BadLocationException ble) {
                    ble.printStackTrace();
                }
            }

            c.text= buf.toString();

        } catch (BadLocationException excp) {
        }
    }

	private StringBuilder getDeclarationLines(IDocument doc, int offset)
			throws BadLocationException {
		IASTDeclaration dec= null;
		IASTTranslationUnit ast= getAST();

		if(ast != null) {
		    dec= findFollowingDeclaration(ast, offset);
		    if(dec == null) {
		        IASTNodeSelector ans= ast.getNodeSelector(ast.getFilePath());
		        IASTNode node= ans.findEnclosingNode(offset, 0);
		        if(node instanceof IASTDeclaration) {
		            dec= (IASTDeclaration) node;
		        }
		    }
		}

		if(dec!=null) {
		    ITypedRegion partition= TextUtilities.getPartition(doc, ICPartitions.C_PARTITIONING /* this! */, offset, false);
		    return customizeAfterNewLineForDeclaration(doc, dec, partition);
		}
		return null;
	}

    private String getCommentPrefix(String indent) throws BadLocationException {
        if (indent.endsWith(SLASH_COMMENT))
        {
            return SLASH_COMMENT;
        }
        else
        {
            return EXCL_COMMENT;
        }
    }
    
	/**
	 * Returns the range of the comment prefix on the given line in
	 * <code>document</code>. The prefix greedily matches the following regex
	 * pattern: <code>\s*\/\/[\/!]</code>, that is, any number of whitespace
	 * characters, followed by an comment ('///' or '//!').
	 *
	 * @param document the document to which <code>line</code> refers
	 * @param line the line from which to extract the prefix range
	 * @return an <code>IRegion</code> describing the range of the prefix on
	 *         the given line
	 * @throws BadLocationException if accessing the document fails
	 */
	protected static IRegion findPrefixRange(IDocument document, IRegion line) throws BadLocationException {
		int lineOffset= line.getOffset();
		int lineEnd= lineOffset + line.getLength();
		int indentEnd= findEndOfWhiteSpaceAt(document, lineOffset, lineEnd);
		if (indentEnd < lineEnd-2 && document.getChar(indentEnd) == '/' && document.getChar(indentEnd+1) == '/' && (
				document.getChar(indentEnd+2) == '/' || document.getChar(indentEnd+2) == '!')) {
			indentEnd += 3;
		}
		return new Region(lineOffset, indentEnd - lineOffset);
	}

}
